﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Remoting.Messaging;
using System.Reflection;

using System.Runtime.Remoting.Proxies;
using MessageCallAspect.Inspector.Attributes;
using MessageCallAspect.Inspector.MessageSink;
using System.Runtime.Remoting;
using System.Security.Permissions;
using MessageCallAspect.Aspect.Cache;
using System.Diagnostics;
using Syk.Library.Extensions;

namespace MessageCallAspect.Inspector
{
    public interface ITargetInspectorPolicy
    {
        Func<IMessageSinkCreator, bool> SinkCreatorPredicate { get; }
    }


   // [DebuggerStepThrough]
    public class TargetInspectorProxy<TTarget> : RealProxy, IDisposable
      where TTarget : class
    {
        #region fields

        private readonly TTarget m_Target;
        private readonly IServiceProvider m_Context;
        private readonly object m_CacheLocker = new object();
        private readonly Dictionary<RuntimeMethodHandle, IMessageSink> m_Cache = new Dictionary<RuntimeMethodHandle, IMessageSink>();
        private readonly Dictionary<RuntimeMethodHandle, List<IMessageSinkCreator>> m_MessageSinkCreatorDic = new Dictionary<RuntimeMethodHandle, List<IMessageSinkCreator>>();

        #endregion

        #region ctor

        public TargetInspectorProxy(TTarget target, IServiceProvider context, params IMessageSinkCreator[] dynamicMessageSinkCreator)
            : base(typeof(TTarget))
        {
            m_Target = target;
            m_Context = context;

            Type targetType = target.GetType();
            IMessageSinkCreator[] classMessageSinkCreators = targetType.GetAllAttributes<MessageSinkAttribute>()
                                                                .Cast<IMessageSinkCreator>().ToArray();

            IEnumerable<MethodInfo> concreteTargetMethods = targetType.GetInterfaceConcreteMethods<TTarget>(true)
                .Where(mi => !mi.HasAttribute<InspectorIgnoreAttribute>());

            ITargetInspectorPolicy targetInspectorPolicy = context.GetService<ITargetInspectorPolicy>();
            foreach (MethodInfo concreteTargetMethod in concreteTargetMethods)
            {
                IMessageSinkCreator[] methodMessageSinkCreators = concreteTargetMethod.GetAllAttributes<MessageSinkAttribute>().Cast<IMessageSinkCreator>().ToArray();
                List<IMessageSinkCreator> messageSinkCreators = methodMessageSinkCreators.Union(classMessageSinkCreators)
                                                                              .Union(dynamicMessageSinkCreator).Distinct(new MessageSinkCreatorComparer()).ToList();
                if (targetInspectorPolicy != null)
                {
                    messageSinkCreators = messageSinkCreators.Where(sc => targetInspectorPolicy.SinkCreatorPredicate(sc)).ToList();
                }
                
                RuntimeMethodHandle key = GetMethodHandle(concreteTargetMethod.GetInterfaceMethod<TTarget>(true));
                List<IMessageSinkCreator> messageSinkCreatorList;
                if (!m_MessageSinkCreatorDic.TryGetValue(key, out messageSinkCreatorList))
                {
                    messageSinkCreatorList = new List<IMessageSinkCreator>();
                    m_MessageSinkCreatorDic.Add(key, messageSinkCreatorList);
                }

                messageSinkCreatorList.AddRange(messageSinkCreators.OrderBy(att => att.Order));
            }
    
        }

        #endregion

        #region SinkCreatorComparer

        private class MessageSinkCreatorComparer : IEqualityComparer<IMessageSinkCreator>
        {
            #region IEqualityComparer<IMessageSinkCreator> Members

            public bool Equals(IMessageSinkCreator x, IMessageSinkCreator y)
            {
                return (x.GetType() == y.GetType());
            }

            public int GetHashCode(IMessageSinkCreator obj)
            {
                return obj.GetType().GetHashCode();
            }

            #endregion
        }

        #endregion

        public TTarget TransparentProxy
        {
            get
            {
                return base.GetTransparentProxy() as TTarget;
            }
        }

        [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.Infrastructure)]
        public override IMessage Invoke(IMessage msg)
        {
            try
            {
                IMessageSink sink = GetMessageSinkChain(msg);
                return sink.SyncProcessMessage(msg);
            }
            catch (Exception ex)
            {
                return ex.Wrap(msg);
            }

        }

        /// <summary>
        /// create message interception sink chain
        /// </summary>
        /// <param name="context"></param>
        /// <param name="target"></param>
        /// <param name="messageSinkCreators"></param>
        /// <returns></returns>
        protected virtual IMessageSink CreateMessageSinkChain(IServiceProvider context, object target, IEnumerable<IMessageSinkCreator> messageSinkCreators)
        {
            IMessageSink nextSink = new EndpointSink(target);
            foreach (IMessageSinkCreator creator in messageSinkCreators.Reverse())
            {
                nextSink = creator.CreateMessageSink(context, m_Target, nextSink);
            }
            return nextSink;
        }

        private IMessageSink GetMessageSinkChain(IMessage msg)
        {
            IMethodCallMessage methodMessage = new MethodCallMessageWrapper((IMethodCallMessage)msg);
            RuntimeMethodHandle handle = GetMethodHandle((MethodInfo)methodMessage.MethodBase);

            if (!m_Cache.ContainsKey(handle))
            {
                lock (m_CacheLocker)
                {
                    if (!m_Cache.ContainsKey(handle))
                    {
                        List<IMessageSinkCreator> matches = new List<IMessageSinkCreator>();
                        if (m_MessageSinkCreatorDic.ContainsKey(handle))
                            matches.AddRange(m_MessageSinkCreatorDic[handle]);

                        IMessageSink messageSinkChain = CreateMessageSinkChain(m_Context, m_Target, matches);
                        m_Cache.Add(handle, messageSinkChain);
                    }
                }
            }
            return m_Cache[handle];
        }

        /// <summary>
        /// get method uid
        /// </summary>
        /// <param name="method"></param>
        /// <returns></returns>
        private static RuntimeMethodHandle GetMethodHandle(MethodInfo method)
        {
            RuntimeMethodHandle handle;

            if (method.IsGenericMethod)
                handle = method.GetGenericMethodDefinition().MethodHandle;
            else
                handle = method.MethodHandle;

            return handle;
        }

        #region IDisposable Members

        public void Dispose()
        {
        }

        #endregion
    }

}
